package com.google.code.iproc;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.Collator;
import java.text.ParseException;
import java.text.RuleBasedCollator;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.google.code.iproc.ToCEntry.Location;
import com.google.code.iproc.util.IntegerRange;
import com.itextpdf.awt.geom.AffineTransform;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BadPdfFormatException;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfAction;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfDestination;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfImportedPage;
import com.itextpdf.text.pdf.PdfIndirectReference;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfOutline;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfRectangle;
import com.itextpdf.text.pdf.PdfWriter;

public class Iproc {

	public static void main(String[] args) throws Exception {
		System.out.println(System.getProperty("os.name"));
		final File outlineFile = new File( args[0] );
		final IprocProcessor p = new IprocProcessor( outlineFile );
		p.run();
	}
	
}

class IprocProcessor {
	
	private final File outlineFile;
	private final Element outlineRoot;

	public IprocProcessor( final File outlineFile ) throws ParserConfigurationException, SAXException, IOException {
		this.outlineFile = outlineFile;
		final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setCoalescing( true );
		final DocumentBuilder db = dbf.newDocumentBuilder();
		final org.w3c.dom.Document doc = db.parse( outlineFile );
		this.outlineRoot = doc.getDocumentElement();
	}

	public File getOutlineFile() {
		return outlineFile;
	}

	public Element getOutlineRoot() {
		return outlineRoot;
	}
	
	public static final FontFactory FONT_FACTORY = new FontFactory();
	
	public static final HeaderFooter HEADER_FOOTER_EVENT = new HeaderFooter( FONT_FACTORY );

	private static final float mm = 72f / 25.4f;
	
	private static final float SCALE = 1.0f;
	
	private static final Rectangle PAGE_SIZE = new Rectangle( 176f*mm, 250f*mm );
	
	private static final float ORIG_MARGIN_LEFT = 35f*mm;
	private static final float ORIG_MARGIN_BOTTOM = 49f*mm;
	
	private static final float MARGIN_TOP_AND_BOTTOM = PAGE_SIZE.getHeight() - 193f*mm;
	static final float MARGIN_TOP = MARGIN_TOP_AND_BOTTOM / 2;
	static final float MARGIN_BOTTOM = MARGIN_TOP_AND_BOTTOM / 2;
	static final float MARGIN_INNER = 19f*mm;
	static final float MARGIN_OUTER = 18f*mm;
	
	
	public static final Rectangle[] PAGE_CONTENTS_RECT = new Rectangle[] {
		new Rectangle( PAGE_SIZE.getLeft() + MARGIN_OUTER, PAGE_SIZE.getBottom() + MARGIN_BOTTOM, PAGE_SIZE.getRight() - MARGIN_INNER, PAGE_SIZE.getTop() - MARGIN_TOP ),
		new Rectangle( PAGE_SIZE.getLeft() + MARGIN_INNER, PAGE_SIZE.getBottom() + MARGIN_BOTTOM, PAGE_SIZE.getRight() - MARGIN_OUTER, PAGE_SIZE.getTop() - MARGIN_TOP ),
	};
	
	public static final AffineTransform TRANSFORM[] = new AffineTransform[] {
		new AffineTransform( SCALE, 0, 0, SCALE, MARGIN_OUTER - SCALE * ORIG_MARGIN_LEFT, MARGIN_BOTTOM - SCALE * ORIG_MARGIN_BOTTOM ),
		new AffineTransform( SCALE, 0, 0, SCALE, MARGIN_INNER - SCALE * ORIG_MARGIN_LEFT, MARGIN_BOTTOM - SCALE * ORIG_MARGIN_BOTTOM ),
	};
	
	private Document document = null;
	private PdfWriter writer = null;
	private int nPages = 0;
	private IntegerRange tocReservedPages = null;
	private final List<ToCEntry> tocEntries = new LinkedList<ToCEntry>();
	private Element tocEl = null;
	private String docTitle = "Book of Proceedings";
	private Deque<PdfOutline> outlineStack = new ArrayDeque<PdfOutline>();

	public void run() throws Exception {
		final NodeList nodes = outlineRoot.getChildNodes();
		for ( int i = 0; i < nodes.getLength(); ++i ) {
			final Node node = nodes.item( i );
			if ( node.getNodeType() == Node.ELEMENT_NODE ) {
				final Element el = (Element) node;
				final String instruction = el.getTagName();
				final boolean followOdd = makeFollowOdd( el );
				if ( "section".equals( instruction ) ) {
					final File inputFile = makeFile( el );
					final String title = makeTitle( el );
					processHeaders( el );
					final ToCEntry.Location location;
					if ( inputFile != null ) {
						final PdfReader reader1 = new PdfReader( inputFile.getAbsolutePath() );
						location = copyAllPages( reader1, inputFile.getName(), title, followOdd );
					} else {
						location = null;
					}
					if ( title.length() > 0 ) {
						final ToCEntry tocEntry = makeToCEntry( el, title, location );
						tocEntries.add( tocEntry );
					}
				} else if ( "output".equals( instruction ) ) {
					endOutputDocument();
					startOutputDocument( makeFile( el ) );
				} else if ( "toc".equals( instruction ) ) {
					final int pageNo = nPages + 1;
					final String s = el.getAttribute( "reserved-pages" );
					final int nReservedPages = ( s.length() > 0 ) ? Integer.valueOf( s ) : 1;
					nPages += nReservedPages;
					tocReservedPages = new IntegerRange( pageNo, pageNo + nReservedPages - 1 );
					tocEl = el;
					System.out.println( "ToC will be pages " + tocReservedPages );
				} else if ( "author-index".equals( instruction ) ) {
					document.newPage();
					document.setPageCount( ++nPages );
					PdfDestination destination = null;
					final int firstPage = document.getPageNumber();
					final ColumnText ct = new ColumnText( writer.getDirectContent() );
					final String title = makeTitle( el );
					if ( title.length() > 0 ) {
						final Chunk p = new Chunk( title, FONT_FACTORY.getArialBold( 13 ) );
						ct.addText( p );
						ct.addText( Chunk.NEWLINE );
						destination = new PdfDestination( PdfDestination.FITH, 600 );
						new PdfOutline( outlineStack.peek(), destination, title );
					}
					processHeaders( el );
					final SortedMap<String, AuthorIndexEntry> authors = makeAuthorIndex( tocEntries );
					char firstChar = ' ';
					for ( final Map.Entry<String, AuthorIndexEntry> e : authors.entrySet() ) {
						final String key = e.getKey().trim();
						final char currentFirstChar = mapFirstCharacter( key.charAt( 0 ) );
						if ( firstChar != currentFirstChar ) {
							firstChar = currentFirstChar;
							final Font letterFont = FONT_FACTORY.getArialBold( 7 );
							ct.addText( new Chunk( "\n", letterFont ) );
	//						ct.addText( new Chunk( String.valueOf( firstChar ), letterFont ) );
	//						ct.addText( Chunk.NEWLINE );
						}
						final AuthorIndexEntry aiEntry = e.getValue();
						final Font indexEntryFont = FONT_FACTORY.getTimesNewRoman( 10 );
						ct.addText( new Chunk( key, indexEntryFont ) );
						String sep = "  ";
						for ( final ToCEntry tocEntry : aiEntry.getOccurrences() ) {
							ct.addText( new Chunk( sep, indexEntryFont ) );
							final Location location = tocEntry.getLocation();
							if ( location != null ) {
								final IntegerRange pageRange = location.getPages();
								if ( pageRange != null ) {
									final Chunk chunk = new Chunk( String.valueOf( pageRange.getMin() ), indexEntryFont );
									if ( location != null ) {
										final PdfDestination dest = location.getDest();
										chunk.setAction( PdfAction.gotoLocalPage( pageRange.getMin(), dest, writer ) );
									}
									ct.addText( chunk );
									sep = ", ";
								}
							}
						}
						ct.addText( Chunk.NEWLINE );
					}
					ct.setAlignment( com.itextpdf.text.Element.ALIGN_LEFT );
					ct.setExtraParagraphSpace( 1.12f );
					ct.setLeading( 0, 1.12f );
					ct.setFollowingIndent( 12 );
					int linesWritten = 0;
					int columnsWritten = 0;
					int column = -1;
					int status = ColumnText.START_COLUMN;
					final float intercolumnSpace = 8f*mm;
					final int nColumns = 3;
					while ( ColumnText.hasMoreText( status ) ) {
						if ( ++column == nColumns ) {
							document.newPage();
							document.setPageCount( ++nPages );
							column = 0;
						}
						final Rectangle pageRectangle = IprocProcessor.PAGE_CONTENTS_RECT[ writer.getPageNumber() % 2 ];
						final float llx = pageRectangle.getLeft() + column * ( pageRectangle.getWidth() + intercolumnSpace ) / nColumns;
						final float urx = llx + ( pageRectangle.getWidth() + intercolumnSpace ) / nColumns - intercolumnSpace;
						final Rectangle columnRectangle = new Rectangle( llx, pageRectangle.getBottom(), urx, pageRectangle.getTop() );
						ct.setSimpleColumn( columnRectangle );
						ct.setYLine( columnRectangle.getTop() );
						status = ct.go();
						linesWritten += ct.getLinesWritten();
						columnsWritten++;
					}
					System.out.println( "Author index: wrote " + linesWritten + " lines in " + columnsWritten + " columns" );
					document.newPage();
	
					if ( title.length() > 0 ) {
						final IntegerRange pages = new IntegerRange( firstPage, document.getPageNumber() );
						final ToCEntry tocEntry = makeToCEntry( el, title, new ToCEntry.Location( pages, destination ) );
						tocEntries.add( tocEntry );
					}
					if ( followOdd && nPages % 2 == 1 ) {
						document.newPage();
						writer.setPageEmpty( false );
						document.setPageCount( ++nPages );
						HEADER_FOOTER_EVENT.omitPageNumber();
						HEADER_FOOTER_EVENT.setHeaders( docTitle );
						System.out.println( "Added a blank page" );
					}
	
				} else if ( "title".equals( instruction ) ) {
					final String title = makeTitle( outlineRoot );
					document.addTitle( title );
					docTitle = title;
					HEADER_FOOTER_EVENT.setHeaders( title );
				} else if ( "toc-section".equals( instruction ) ) {
					final String s = extractString( el );
					tocEntries.add( new ToCSection( s ) );
					if ( s.length() > 0 ) {
						final PdfDestination destination = new PdfDestination( PdfDestination.FITH, 600 );
						outlineStack.push( new PdfOutline( outlineStack.peek(), destination, s, false ) );
					}
				} else if ( "toc-section-end".equals( instruction ) ) {
					tocEntries.add( new ToCSectionEnd() );
					if ( outlineStack.size() > 1 ) {
						outlineStack.pop();
					}
				} else if ( "empty-page".equals( instruction ) ) {
					document.newPage();
					writer.setPageEmpty( false );
					document.setPageCount( ++nPages );
					HEADER_FOOTER_EVENT.omitPageNumber();
					HEADER_FOOTER_EVENT.setHeaders( docTitle );
					System.out.println( "Added a blank page" );
				}
			}
		}
		endOutputDocument();
	}

	private void processHeaders( final Element el ) {
		final String x[] = new String[2];
		final int rem = 1 - document.getPageNumber() % 2;
		final NodeList nodes = el.getChildNodes();
		for ( int i = 0; i < nodes.getLength(); ++i ) {
			final Node node = nodes.item( i );
			if ( node.getNodeType() == Node.ELEMENT_NODE ) {
				final Element el1 = (Element) node;
				final String el1Type = el1.getTagName();
				if ( "title".equals( el1Type ) ) {
					final String title = extractString( el1 );
					x[rem] = title;
				} else if ( "header".equals(  el1Type  ) ) {
					final String value = extractString( el1 );
					final String which = el1.getAttribute( "which" );					
					if ( "all".equals( which ) || "odd".equals( which ) )
						x[rem] = value;
					if ( "all".equals( which ) || "even".equals( which ) )
						x[1-rem] = value;
				} else if ( "authors".equals( el1Type ) ) {
					final StringBuilder sb = new StringBuilder();
					final List<String> authorsList = makeAuthorsList( el );
					final int n = authorsList.size();
					for ( int i1 = 0; ++i1 <= n; ) {
						if ( i1 > 1 && i1 < n )
							sb.append( ", " );
						if ( i1 > 1 && i1 == n )
							sb.append( " and " );
						sb.append( authorsList.get( i1 - 1 ) );
					}
					x[1-rem] = sb.toString();
				}
			}
		}
		if ( x[0] == null )
			x[0] = docTitle;
		if ( x[1] == null )
			x[1] = x[0];
		HEADER_FOOTER_EVENT.setHeaderEven( x[0] );
		HEADER_FOOTER_EVENT.setHeaderOdd( x[1] );
	}

	private ToCEntry makeToCEntry( final Element el, final String title, final Location location ) {
		final List<String> authors = makeAuthorsList( el );
		return new ToCEntry( title, authors, location, outlineStack.size() - 1 );
	}

	private List<String> makeAuthorsList( final Element el ) {
		final List<String> authors = new LinkedList<String>();
		final Element authorsEl = (Element) el.getElementsByTagName( "authors" ).item( 0 );
		if ( authorsEl != null ) {
			final NodeList authorsNodeList = authorsEl.getElementsByTagName( "author" );
			for ( int j = 0; j < authorsNodeList.getLength(); ++j ) {
				final Element authorEl = (Element) authorsNodeList.item( j );
				final String authorName = authorEl.getTextContent();
				authors.add( authorName );
			}
		}
		return authors;
	}

	private boolean makeFollowOdd( final Element el ) {
		final String s = el.getAttribute( "follow-odd" );
		return !( "false".equals( s ) || "no".equals( s ) || "off".equals( s ) );
	}
	
	private char mapFirstCharacter( final char c ) {
		switch ( c ) {
		case 'Ø':
			return 'O';
		default:
			return c;
		}
	}
	
	private SortedMap<String, AuthorIndexEntry> makeAuthorIndex( final List<ToCEntry> tocEntries ) throws ParseException {
		final RuleBasedCollator collator = new RuleBasedCollator( ( (RuleBasedCollator) Collator.getInstance( Locale.GERMAN ) ).getRules() + " & o < ø & O < Ø & ø < p & Ø < P" );
		final SortedMap<String, AuthorIndexEntry> authors = new TreeMap<String, AuthorIndexEntry>( collator );
		for ( final ToCEntry tocEntry : tocEntries ) {
			final List<String> authors2 = tocEntry.getAuthors();
			if ( authors2 != null ) {
				for ( final String authorName : authors2 ) {
					final String authorNameReversed = reverseAuthorName( authorName );
					if (! authors.containsKey( authorNameReversed ) ) {
						authors.put( authorNameReversed, new AuthorIndexEntry( authorName ) );
					}
					final AuthorIndexEntry aiEntry = authors.get( authorNameReversed );
					aiEntry.addOccurrence( tocEntry );
				}
			}
		}
		return authors;
	}

	private static final Map<String, String> authorNameExceptions = new HashMap<String, String>();
	static {
		authorNameExceptions.put( "Bojana Dimić Surla", "Dimić Surla, Bojana" );
		authorNameExceptions.put( "Rosa Di Cesare", "Di Cesare, Rosa" );
		authorNameExceptions.put( "Petr Ráb (moderator)", "Ráb, Petr" );
	}
	
	private static String reverseAuthorName( final String authorName ) {
		final String converted = authorNameExceptions.get( authorName ); 
		if ( converted != null )
			return converted;
		final int x = authorName.lastIndexOf( ' ' );
		return authorName.substring( x + 1 ) + ", " + authorName.substring( 0, x );
	}
	
	private void makeToCEntryPhrase( final PdfPTable t, final ToCEntry tocEntry ) throws DocumentException, IOException {
		final float indentStep = 8f;
		if ( tocEntry instanceof ToCSection ) {
			final Phrase phrase = new Phrase();
			final Font titleFont = FONT_FACTORY.getArial( 10 );
			phrase.add( new Chunk( tocEntry.getTitle(), titleFont ) );			
			final PdfPCell cell1 = new PdfPCell( phrase );
			cell1.setBorder( 0 );
			cell1.setPaddingTop( 10 );
			cell1.setPaddingBottom( 5 );
			cell1.setPaddingLeft( tocEntry.getIndentLevel() * indentStep );
			cell1.setPaddingRight( 5 );
			cell1.setVerticalAlignment( PdfPCell.ALIGN_TOP );
			t.addCell( cell1 );
			final PdfPCell cell2 = new PdfPCell();
			cell2.setBorder( 0 );
			t.addCell( cell2 );
		} else if ( tocEntry instanceof ToCSectionEnd ) {
			// noop
		} else {
			final Phrase phrase = new Phrase();
			final Font titleFont = FONT_FACTORY.getTimesNewRoman( 10 );
			phrase.add( new Chunk( tocEntry.getTitle(), titleFont ) );
			String sep = "\n";
			final Font authorsFont = FONT_FACTORY.getTimesNewRomanItalic( 10 );
			
			final List<String> authors2 = tocEntry.getAuthors();
			if ( authors2 != null ) {
				for ( final String author : authors2 ) {
					phrase.add( new Chunk( sep, authorsFont ) );
					phrase.add( new Chunk( author, authorsFont ) );
					sep = ", ";
				}
			}
			final PdfPCell cell1 = new PdfPCell( phrase );
			cell1.setBorder( 0 );
			cell1.setPadding( 2 );
			cell1.setPaddingLeft( tocEntry.getIndentLevel() * indentStep );
			cell1.setPaddingRight( 5 );
			cell1.setVerticalAlignment( PdfPCell.ALIGN_TOP );
			t.addCell( cell1 );
			final Location location = tocEntry.getLocation();
			final IntegerRange pageRange = ( location != null ) ? location.getPages() : null;
			final String pages = ( pageRange != null ) ? String.valueOf( pageRange.getMin() ) : "";
			final Chunk chunk2 = new Chunk( pages, titleFont );
			if ( location != null ) {
				final PdfDestination destination = location.getDest();
				chunk2.setAction( PdfAction.gotoLocalPage( pageRange.getMin(), destination, writer ) );
			}
			final Phrase phrase2 = new Phrase( chunk2 );
			final PdfPCell cell2 = new PdfPCell( phrase2 );
			cell2.setBorder( 0 );
			cell2.setPadding( 2 );
			cell2.setPaddingLeft( 0 );
			cell2.setPaddingRight( 0 );
			cell2.setVerticalAlignment( PdfPCell.ALIGN_TOP );
			cell2.setHorizontalAlignment( com.itextpdf.text.Element.ALIGN_RIGHT );
			t.addCell( cell2 );
		}
	}
	
	private String makeTitle( final Element el ) {
		final Node titleEl = el.getElementsByTagName( "title" ).item( 0 );
		return extractString( titleEl );
	}

	private static String extractString( final Node el ) {
		return ( el != null ) ? el.getTextContent().replaceAll( "(\\t|\\r?\\n)", " " ).replaceAll( "  *", " " ) : "";
	}

	private File makeFile( final Element el ) {
		final String filename = el.getAttribute( "file" );
		return ( filename.length() > 0 ) ? new File( outlineFile.getParentFile(), filename ) : null;
	}

	private void startOutputDocument( final File outputFile ) throws FileNotFoundException, DocumentException {
		final FileOutputStream os = new FileOutputStream( outputFile );
		document = new Document( PAGE_SIZE );
		writer = PdfWriter.getInstance(document, os);
		writer.setPDFXConformance( PdfWriter.PDFX1A2001 );
		writer.setPdfVersion( PdfWriter.VERSION_1_5 );
//		writer.setViewerPreferences( PdfWriter.PageLayoutTwoPageRight + PdfWriter.PageModeUseOutlines );
		writer.setViewerPreferences( PdfWriter.PageLayoutSinglePage + PdfWriter.PageModeUseOutlines );
		writer.setCropBoxSize( PAGE_SIZE );
		writer.setPageEvent( HEADER_FOOTER_EVENT );
		writer.setPageEmpty( false );
		writer.setLinearPageMode();
		writer.setCompressionLevel( 0 );
		document.open();
		outlineStack.push( writer.getRootOutline() );
		nPages = 0;
	}

	private void endOutputDocument() {
		if ( document != null ) {
			if ( tocReservedPages != null ) {
				try {
					document.newPage();
					final int x = nPages;
					try {
						nPages = tocReservedPages.getMin() - 1;
						document.setPageCount( ++nPages );
						final String tocTitle = makeTitle( tocEl );
						if ( tocTitle != null && tocTitle.length() > 0 ) {
							final Paragraph p = new Paragraph( tocTitle, FONT_FACTORY.getArialBold( 13 ) );
							p.setSpacingAfter( 12 );
							document.add( p );
						}
						processHeaders( tocEl );
						final PdfOutline root = outlineStack.peek();
						final PdfDestination destination = new PdfDestination( PdfDestination.FITH, 625 );
						final ArrayList<PdfOutline> kids = root.getKids();
						kids.add( 0, new PdfOutline( root, destination, tocTitle ) );
						kids.remove( kids.size() - 1 );
						root.setKids( kids );
						final PdfPTable t = new PdfPTable( 2 );
						t.setWidthPercentage( 100 );
						t.setWidths( new float[] { 0.87f, 0.13f } );
						for ( final ToCEntry tocEntry : tocEntries ) {
							makeToCEntryPhrase( t, tocEntry );
						}
						document.add( t );
						document.newPage();
						
//						nPages = document.getPageNumber() + 1;
//						System.out.println( "nPages=" + nPages );
//						while ( tocReservedPages.contains( nPages ) ) {
//							document.newPage();
//							writer.setPageEmpty( false );
//							document.setPageCount( ++nPages );
//							System.out.println( "Added page " + nPages );
//						}
						int total;
						while ( ( total = writer.reorderPages( null ) ) < x ) {
							document.newPage();
							writer.setPageEmpty( false );
							nPages = total - x + tocReservedPages.getMax() + 1;
							System.out.println( "nPages=" + nPages + ", total=" + total + ", x=" + x );
							document.setPageCount( nPages );
							System.out.println( "Added page " + nPages );
							document.newPage();
						}
						System.out.println( "nPages=" + nPages + ", total=" + total );
						int[] order = new int[ total ];
						for ( int i = 1; i <= total; i++ ) {
							order[i-1] = ( i < tocReservedPages.getMin() ) ? i :
								( i <= tocReservedPages.getMax() ) ? i - tocReservedPages.getMax() + total:
								i - tocReservedPages.size() ;
							System.out.print( order[i-1] + " " );
						}
						System.out.println();
						writer.reorderPages( order );
					} finally {
						nPages = x;
					}
				} catch ( Exception e ) {
					e.printStackTrace();
				}
			}
			document.close();
			System.out.println( "Wrote " + nPages + " pages to output file" );
		}
		document = null;
		writer = null;
		nPages = 0;
	}

	private Location copyAllPages( PdfReader reader, String filename, String title, boolean followOdd ) throws IOException, BadPdfFormatException {
		PdfDestination destination = null;
		if ( title != null && title.length() > 0 ) {
			destination = new PdfDestination( PdfDestination.FITH, 600 );
			new PdfOutline( outlineStack.peek(), destination, title );
		}
		final Rectangle cropBox = reader.getCropBox( 1 );
		final PdfContentByte cb = writer.getDirectContent();
		int n = reader.getNumberOfPages();
		int firstPage = 0, lastPage = 0;
		for (int page = 0; ++page <= n;) {
			final PdfDictionary pageDict = reader.getPageN( page );
	//		pageDict.put( PdfName.MEDIABOX, new PdfRectangle( PAGE_SIZE ) );
			pageDict.put( PdfName.ARTBOX, new PdfRectangle( cropBox ) );
			document.newPage();
			document.setPageCount( ++nPages );
			final PdfImportedPage p = writer.getImportedPage(reader, page);
			cb.addTemplate( p, TRANSFORM[page%2] );
			createBounds( pageDict, cropBox, cb );
			document.newPage();
			if ( page == 1 ) {
				firstPage = document.getPageNumber();
			} 
			if ( page == n ) {
				lastPage = document.getPageNumber();
			}
		}
		System.out.println( "Copied " + n + " pages from " + filename + ", pages " + firstPage + "-" + lastPage );
		if ( followOdd && n % 2 == 1 ) {
			document.newPage();
			writer.setPageEmpty( false );
			document.setPageCount( ++nPages );
			HEADER_FOOTER_EVENT.omitPageNumber();
			HEADER_FOOTER_EVENT.setHeaders( docTitle );
			System.out.println( "Added a blank page" );
		}
		document.newPage();
		return new ToCEntry.Location( new IntegerRange( firstPage, lastPage ), destination );
	}
	
	static void createBounds( final PdfDictionary pageDict, final Rectangle mediabox, final PdfContentByte cb ) {
		pageDict.put( PdfName.MEDIABOX, new PdfRectangle( mediabox ) );
	//	cb.saveState();
	//	cb.setLineWidth( 0.001f );
	//	cb.setCMYKColorStroke( 0, 0, 0, 127 );
	//	cb.moveTo( mediabox.getLeft(), mediabox.getTop() );
	//	cb.lineTo( mediabox.getLeft(), mediabox.getBottom() );
	//	cb.lineTo( mediabox.getRight(), mediabox.getBottom() );
	//	cb.lineTo( mediabox.getRight(), mediabox.getTop() );
	//	cb.closePathStroke();
	//	cb.restoreState();
	}
	
}